//
//  ConnectionsController.m
//  Genesis
//
//  Created by Kristoffer Andersen on 13/03/09.
//  Copyright 2009 Kristoffer Andersen. All rights reserved.
//
//  This file is part of Genesis.
//  
//  Genesis is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//  
//  Genesis is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//  
//  You should have received a copy of the GNU General Public License
//  along with Genesis.  If not, see <http://www.gnu.org/licenses/>.

#import "ConnectionsController.h"
#include <unistd.h>


@implementation ConnectionsController


////////////////////////////////////////////////////////////////
//// Initializers

-(id)init
{
	self = [super init];
	connections = [[NSMutableArray alloc] init];
    passwordLock = [[NSRecursiveLock alloc] init];
	
	return self;
}

-(void)awakeFromNib
{
	NSTableColumn *conColumn = [connectionTable tableColumnWithIdentifier:@"conLamp"];
	[conColumn setDataCell:[[GProgressIndicatorCell alloc] init]];
	
	//Load connections from fdisk
	NSString *path = [NSString stringWithFormat:@"%@/conns.gen",[[NSBundle mainBundle] resourcePath]];
	NSData *serConn = [[NSData alloc] initWithContentsOfFile:path];
	if (serConn != nil) {
		[connections release];
		connections = [NSKeyedUnarchiver unarchiveObjectWithData:serConn];
		[connections retain];
		NSEnumerator *oEnum = [connections objectEnumerator];
		GConnection *conn;
		while (conn = [oEnum nextObject]) {
			[conn setDelegate:self];
		}
	}
	else {

	}
	
	[[[connectionTable tableColumnWithIdentifier:@"conLamp"] dataCell] addIndicator];
	[connectionTable reloadData];
	
}


////////////////////////////////////////////////////////////////
//// View Actions

-(IBAction)addConnButton:(id)sender
{
	//Run new connection window!
	[self willChangeValueForKey:@"selectedConnection"];
	selectedConnection = [[GConnection alloc] init];
	[selectedConnection setDelegate:self];
	[self didChangeValueForKey:@"selectedConnection"];
	[connSheetSaveButton setAction:@selector(saveAddConnButton:)];
	[connSheetSaveButton setTitle:@"Create"];
	[connSheetCancelButton setHidden:NO];
	
	[self reloadConnWindowFields];
	
	//Run sheet
	[NSApp beginSheet: newConnWindow
	   modalForWindow: [sender window]
		modalDelegate: self
	   didEndSelector: @selector(newConnWindowDidEndSheet:returnCode:contextInfo:)
		  contextInfo: nil];
}

-(IBAction)cancelAddConnButton:(id)sender
{
	[NSApp endSheet:newConnWindow returnCode: -1];
}

-(IBAction)saveAddConnButton:(id)sender
{
	//Save the viewBoundConnection
	[connections addObject:selectedConnection];
	[selectedConnection setDelegate:self];
	[[[connectionTable tableColumnWithIdentifier:@"conLamp"] dataCell] addIndicator];
	[self saveAllConnectionsToDisk];
	NSLog(@"Add new conn");
	
	[NSApp endSheet:newConnWindow returnCode: 0];
}

-(void)newConnWindowDidEndSheet:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
{
	[self willChangeValueForKey:@"selectedConnection"];
	[selectedConnection release];
	selectedConnection = nil;
	[self didChangeValueForKey:@"selectedConnection"];
	[sheet orderOut:self];
	[connectionTable reloadData];
}

-(IBAction)removeConnButton:(id)sender
{
	if ([connectionTable selectedRow] != -1 && [[connections objectAtIndex:[connectionTable selectedRow]] connected] != YES) {
		NSBeginAlertSheet([[NSString alloc] initWithFormat:@"Delete %@?",[[connections objectAtIndex:[connectionTable selectedRow]] connectionName]],
						  @"Delete",
						  @"Cancel",
						  nil,
						  [sender window],
						  self,
						  @selector(removeConfirmSheetClosed:returnCode:contextInfo:),
						  NULL,
						  sender,
						  @"Are you sure?");
		
		
	}
	else {
		//No item selected
		NSBeep();
	}
}

-(void)removeConfirmSheetClosed:(NSWindow *)sheet
		returnCode:(int)returnCode
	   contextInfo:(void *)contextInfo
{
	//do the stuff
	if (returnCode == NSAlertDefaultReturn) {
		[[[[connectionTable tableColumnWithIdentifier:@"conLamp"] dataCell] indicatorAtIndex:[connectionTable selectedRow]] removeFromSuperview];
		
		[connections removeObjectAtIndex:[connectionTable selectedRow]];
		[connectionTable reloadData];
	}
}

-(IBAction)editConnButton:(id)sender
{
	if ([connectionTable selectedRow] != -1 && [[connections objectAtIndex:[connectionTable selectedRow]] connected] != YES) {
		NSLog(@"Seleceted row: %i",[connectionTable selectedRow]);
		
		[connSheetSaveButton setAction:@selector(saveEditConnButton:)];
		[connSheetSaveButton setTitle:@"Close"];
		[connSheetCancelButton setHidden:YES];
		
		[self willChangeValueForKey:@"selectedConnection"];
		selectedConnection = [connections objectAtIndex:[connectionTable selectedRow]];
		[selectedConnection retain];
		[self didChangeValueForKey:@"selectedConnection"];
		[self reloadConnWindowFields];
		
		
		//Run sheet
		[NSApp beginSheet: newConnWindow
		   modalForWindow: [sender window]
			modalDelegate: self
		   didEndSelector: @selector(newConnWindowDidEndSheet:returnCode:contextInfo:)
			  contextInfo: nil];
	}
	else {
		//No item selected
		NSBeep();
	}
}

-(IBAction)saveEditConnButton:(id)sender
{
	//Save the viewBoundConnection
	//[connections addObject:selectedConnection];
	NSLog(@"Do nothing, but update");
	[NSApp endSheet:newConnWindow returnCode: 0];
	[self saveAllConnectionsToDisk];
	[connectionTable reloadData];
}

-(IBAction)tableConnectButton:(id)sender
{
	NSLog(@"Preparing conenction for %@",[[connections objectAtIndex:[connectionTable selectedRow]] connectionName]);
	// conTable -> Column -> Cell ->indicator -> start animation
	//[[[[connectionTable tableColumnWithIdentifier:@"conLamp"] dataCell] indicatorAtIndex:[connectionTable selectedRow]] startAnimation:sender];
	
	int pStatus = [[connections objectAtIndex:[connectionTable selectedRow]] prepareConnection];
	if (pStatus == 0) {
		NSLog(@"Opening connection to: %@",[[connections objectAtIndex:[connectionTable selectedRow]] tunnelHost]);
		[[connections objectAtIndex:[connectionTable selectedRow]]
            performSelectorInBackground:@selector(createConnection)
                             withObject:nil];
		[connectionTable reloadData];
	}
	else NSLog(@"Error, prepare returned: %i",pStatus);
	
}

-(IBAction)tableDisconnectButton:(id)sender
{
	NSLog(@"Disconnecting conenction for %@",[[connections objectAtIndex:[connectionTable selectedRow]] connectionName]);
	// conTable -> Column -> Cell ->indicator -> start animation
	//[[[[connectionTable tableColumnWithIdentifier:@"conLamp"] dataCell] indicatorAtIndex:[connectionTable selectedRow]] startAnimation:sender];
	
	if ([[connections objectAtIndex:[connectionTable selectedRow]] connected]) {
		NSLog(@"Closing connection to: %@",[[connections objectAtIndex:[connectionTable selectedRow]] tunnelHost]);
		[[connections objectAtIndex:[connectionTable selectedRow]] terminateConnection];
		/*[[[connectionTable tableColumnWithIdentifier:@"conLamp"] dataCell] setTitle:@"Disconnecting..."];
		[[[connectionTable tableColumnWithIdentifier:@"conLamp"] dataCell] setEnabled:NO];*/
		[connectionTable reloadData];
	}
	else NSLog(@"Error, not connected!");
	
}

-(IBAction)tableConsoleButton:(id)sender
{
	
	
	NSDictionary *attribs = [[NSDictionary alloc] initWithObjectsAndKeys:NSForegroundColorAttributeName, [NSColor greenColor], nil];
	
	
	if ([[connections objectAtIndex:[connectionTable selectedRow]] console] != nil) {
		//Data in console
		NSAttributedString *content = [[NSAttributedString alloc] initWithString:[[connections objectAtIndex:[connectionTable selectedRow]] console] attributes:attribs];
		
		[[consoleView textStorage] setAttributedString:content];
		NSRange range = NSMakeRange(0, [content length]);
		[consoleView setTextColor:[NSColor whiteColor] range:range];
	}
	else {
		//no output
		[[consoleView textStorage] setAttributedString:[[NSAttributedString alloc] initWithString:@""]];
	}
	
	if (!consoleWindowController) {
		consoleWindowController = [[NSWindowController alloc] initWithWindow:consolePanel];
	}	
	
	[consoleWindowController showWindow:sender];
	
	NSLog(@"Console for %@",[[connections objectAtIndex:[connectionTable selectedRow]] connectionName]);

}

-(IBAction)tableSettingsButton:(id)sender
{
	[self editConnButton:sender];
}

-(void)saveAllConnectionsToDisk
{
	//Serialize connection presets
	NSString *path = [NSString stringWithFormat:@"%@/conns.gen",[[NSBundle mainBundle] resourcePath]];
	NSLog(@"Saving at: %@", path);
	NSData *serConn = [NSKeyedArchiver archivedDataWithRootObject:connections];
	[serConn writeToFile:path atomically:NO];
}

-(IBAction) passwordEntered:(id)sender
{
    [passwordDialog orderOut:self];
    NSString *password = [passwordField stringValue];
    [passwordField setStringValue:@""];
    [passwordConnection setPassword:password];
    [passwordConnection.flag signal];
    [passwordConnection.flag unlock];
}


////////////////////////////////////////////////////////////////
//// TableData Source protocol

-(int)numberOfRowsInTableView:(NSTableView *)aTableView
{
	//NSLog(@"TBData count: %i",[connections count]);
	return [connections count];
}

-(id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
{
	//name column
	if ([[aTableColumn identifier] isEqualToString:@"connectionName"]) {
		return [[connections objectAtIndex:rowIndex] connectionName];
	}
	//Service column
	else if ([[aTableColumn identifier] isEqualToString:@"connectionService"]) {
		return [[connections objectAtIndex:rowIndex] serviceType];
	}
	
	return nil;
}

- (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
{
	if (false && [[aTableColumn identifier] isEqualToString:@"conLamp"]) {
		
		if ([[connections objectAtIndex:rowIndex] interrupted]) {
			[aTableView addSubview:[aCell isInterrupted]];
		}
		else {
			if ([[connections objectAtIndex:rowIndex] connected]) {
				[[aCell indicatorAtIndex:rowIndex] stopAnimation:self];
				[aCell setProgressAtIndex:rowIndex];
				[aTableView addSubview:[aCell isConnected]];
			}
			else if ([[connections objectAtIndex:rowIndex] connecting]) {
				[[aCell indicatorAtIndex:rowIndex] startAnimation:self];
				[aCell setProgressAtIndex:rowIndex];
				[aTableView addSubview:[aCell indicatorAtIndex:rowIndex]];
			}
			else {
				//NSLog(@"Controller req: green!");
				[aTableView addSubview:[aCell isConnected]];
			}
				
		}
	}
	
	//connect button column
	else if ([[aTableColumn identifier] isEqualToString:@"connect"]) {
		if (![[connections objectAtIndex:rowIndex] connecting] && ![[connections objectAtIndex:rowIndex] connected]) {
			[aCell setTitle:@"Connect"];
			[aCell setEnabled:YES];
			[aCell setAction:@selector(tableConnectButton:)];
		}
		else if ([[connections objectAtIndex:rowIndex] connecting]) {
			[aCell setTitle:@"Connecting"];
			[aCell setEnabled:NO];
		}
		else if ([[connections objectAtIndex:rowIndex] connected]) {
			[aCell setTitle:@"Disconnect"];
			[aCell setEnabled:YES];
			[aCell setAction:@selector(tableDisconnectButton:)];
		}
		else if ([[connections objectAtIndex:rowIndex] interrupted]) {
			[aCell setTitle:@"Connect"];
			[aCell setEnabled:YES];
			[aCell setAction:@selector(tableConnectButton:)];
		}
		
	}
}

////////////////////////////////////////////////////////////////
//// Accessors for Connection Options
-(NSString *)connectionName
{
	if ([selectedConnection connectionName] != nil) return [selectedConnection connectionName];
	else return @"";
}
-(NSString *)tunnelHost
{
	if ([selectedConnection tunnelHost] != nil) return [selectedConnection tunnelHost];
	else return @"";
}
-(NSString *)forwardHost
{
	if ([selectedConnection forwardHost] != nil) return [selectedConnection forwardHost];
	else return @"";
}
-(unsigned int)forwardPort
{
	if ([selectedConnection forwardPort] != 0) return [selectedConnection forwardPort];
	else return 0;
}
-(NSString *)localHost
{
	if ([selectedConnection localHost] != nil) return [selectedConnection localHost];
	else return @"";
}
-(unsigned int)localPort
{
	if ([selectedConnection localPort] != 0) return [selectedConnection localPort];
	else return 0;
}
-(NSString *)serviceType
{
	if ([selectedConnection serviceType] != nil ) return [selectedConnection serviceType];
	else return @"";
}
-(NSString *)serviceRecord
{
	if ([selectedConnection serviceRecord] != nil ) return [selectedConnection serviceRecord];
	else return @"";
}
-(NSString *)serviceName
{
	if ([selectedConnection serviceName] != nil) return [selectedConnection serviceName];
	else return @"";
}
-(NSString *)tunnelUsername
{
	if ([selectedConnection tunnelUsername] != nil) return [selectedConnection tunnelUsername];
	else return @"";
}
-(bool)useBonjour
{
	return [selectedConnection useBonjour];
}

-(void)setConnectionName:(NSString *)newConnectionName
{
	[selectedConnection setConnectionName:newConnectionName];
	[selectedConnection setServiceName:newConnectionName]; //Service same as connectionName
}
-(void)setTunnelHost:(NSString *)newTunnelHost
{
	[selectedConnection setTunnelHost:newTunnelHost];
}
-(void)setForwardHost:(NSString *)newForwardHost
{
	[selectedConnection setForwardHost:newForwardHost];
}
-(void)setForwardPort:(unsigned int)newForwardPort
{
	[selectedConnection setForwardPort:newForwardPort];
}
-(void)setLocalHost:(NSString *)newLocalHost
{
	[selectedConnection setLocalHost:newLocalHost];
}
-(void)setLocalPort:(unsigned int)newLocalPort
{
	[selectedConnection setLocalPort:newLocalPort];
}
-(void)setServiceType:(NSString *)newServiceType
{
	// Do 3 tasks: find correct bonjour record, set localPort and remotePort
	[selectedConnection setServiceType:newServiceType];
	
	NSString *lowerCase = [newServiceType lowercaseString];
	NSString *record;
	unsigned int localPort, remotePort;
	[lowerCase rangeOfString:@"test"];
	if ([lowerCase rangeOfString:@"afp"].location != NSNotFound) {
		record = @"_afpovertcp._tcp.";
		remotePort = 548;
		localPort = 5480;
	}
	else if ([lowerCase rangeOfString:@"smb"].location != NSNotFound) {
		record = @"_smb._tcp";
		remotePort = 445;
		localPort = 4450;
	}
	else if ([lowerCase rangeOfString:@"nfs"].location != NSNotFound) {
		record = @"_nfs._tcp";
		remotePort = 2049;
		localPort = 4920;
	}
	else if ([lowerCase rangeOfString:@"itunes"].location != NSNotFound && [lowerCase rangeOfString:@"music"].location != NSNotFound) {
		record = @"_daap._tcp";
		remotePort = 3689;
		localPort = 8936;
	}
	else if ([lowerCase rangeOfString:@"airtunes"].location != NSNotFound) {
		record = @"_raop._tcp";
		remotePort = 5000;
		localPort = 5055;
	}
	else if ([lowerCase rangeOfString:@"airport"].location != NSNotFound && [lowerCase rangeOfString:@"station"].location != NSNotFound) {
		record = @"_airport._tcp";
		remotePort = 5009;
		localPort = 9005;
	}
	else if ([lowerCase rangeOfString:@"http"].location != NSNotFound && [lowerCase rangeOfString:@"https"].location == NSNotFound) {
		record = @"_http._tcp";
		remotePort = 80;
		localPort = 8080;
	}
	else if ([lowerCase rangeOfString:@"https"].location != NSNotFound) {
		record = @"_https._tcp";
		remotePort = 443;
		localPort = 4430;
	}
	else {
		record = @"";
		remotePort = 0;
		localPort = 0;
	}
	
	//assign to accesors
	[self willChangeValueForKey:@"serviceRecord"];
	[self willChangeValueForKey:@"localPort"];
	[self willChangeValueForKey:@"forwardPort"];
	[selectedConnection setServiceRecord:record];
	[selectedConnection setLocalPort:localPort];
	[selectedConnection setForwardPort:remotePort];
	[self didChangeValueForKey:@"serviceRecord"];
	[self didChangeValueForKey:@"localPort"];
	[self didChangeValueForKey:@"forwardPort"];
}
-(void)setServiceRecord:(NSString *)newServiceRecord
{
	[selectedConnection setServiceRecord:newServiceRecord];
}
-(void)setServiceName:(NSString *)newServiceName
{
	[selectedConnection setServiceName:newServiceName];
}
-(void)setTunnelUsername:(NSString *)newTunnelUsername
{
	[selectedConnection setTunnelUsername:newTunnelUsername];
}
-(void)setUseBonjour:(bool)newUseBonjour
{
	[selectedConnection setUseBonjour:newUseBonjour];
}

-(void)reloadConnWindowFields
{
	[self willChangeValueForKey:@"connectionName"];
	[connNameField setStringValue:[self connectionName]];
	[self didChangeValueForKey:@"connectionName"];
	
	[self willChangeValueForKey:@"tunnelHost"];
	[tunnelHostField setStringValue:[self tunnelHost]];
	[self didChangeValueForKey:@"tunnelHost"];
	
	[self willChangeValueForKey:@"serviceType"];
	[serviceTypeField setStringValue:[self serviceType]];
	[self didChangeValueForKey:@"serviceType"];
	
	[self willChangeValueForKey:@"tunnelUsername"];
	[tunnelUsernameField setStringValue:[self tunnelUsername]];
	[self didChangeValueForKey:@"tunnelUsername"];
	
	[self willChangeValueForKey:@"forwardHost"];
	[forwardHostField setStringValue:[self forwardHost]];
	[self didChangeValueForKey:@"forwardHost"];
	
	[self willChangeValueForKey:@"forwardPort"];
	[forwardPortField setIntValue:[self forwardPort]];
	[self didChangeValueForKey:@"forwardPort"];
	
	[self willChangeValueForKey:@"localHost"];
	[localHostField setStringValue:[self localHost]];
	[self didChangeValueForKey:@"localHost"];
	
	[self willChangeValueForKey:@"localPort"];
	[localPortField setIntValue:[self localPort]];
	[self didChangeValueForKey:@"localPort"];
	
	[self willChangeValueForKey:@"serviceName"];
	[serviceRecordField setStringValue:[self serviceType]];
	[self didChangeValueForKey:@"serviceName"];
	
	[self willChangeValueForKey:@"useBonjour"];
	[useBonjourButton setState:[self useBonjour]];
	[self didChangeValueForKey:@"useBonjour"];
}

////////////////////////////////////////////////////////////////
//// Observers

-(void)connectionSucces:(GConnection *)aConnection
{
	[connectionTable reloadData];
    
    //TODO You can't know that the correct row is selected
    //[[[[connectionTable tableColumnWithIdentifier:@"conLamp"] dataCell] indicatorAtIndex:[connectionTable selectedRow]] stopAnimation:self];
    [[[connectionTable tableColumnWithIdentifier:@"conLamp"] dataCell]
                                 indicatorAtIndex:[connections indexOfObject:aConnection]
                                    stopAnimation:self];
	//Inform delegate!
	if (myDelegate != nil) {
		[myDelegate activateStatusMenu];
		[myDelegate aConnectionSucceeded];
	}
}

-(void)connectionInterupted:(GConnection *)aConnection
{
	[connectionTable reloadData];
}

-(void)tunnelDidPostTextNotification:(NSNotification *)aNotification
{
	if ([consolePanel isVisible]) {
		NSDictionary *attribs = [[NSDictionary alloc] initWithObjectsAndKeys:NSForegroundColorAttributeName, [NSColor greenColor], nil];
		
		//Data in console
		NSAttributedString *content = [[NSAttributedString alloc] initWithString:[[connections objectAtIndex:[connectionTable selectedRow]] console] attributes:attribs];
		
		[[consoleView textStorage] setAttributedString:content];
		NSRange range = NSMakeRange(0, [content length]);
		[consoleView setTextColor:[NSColor whiteColor] range:range];
		[consoleView scrollRangeToVisible:NSMakeRange([content length]-1, 0)];
	}
	[connectionTable reloadData];
}

////////////////////////////////////////////////////////////////
//// Delegate specific

-(void)setDelegate:(id)theDelegate
{
	[myDelegate release];
	myDelegate = theDelegate;
	[myDelegate retain];
}
-(id)delegate
{
	if (myDelegate != nil) {
		return myDelegate;
	}
	else return nil;
}

-(void)terminateAllOpenConnections
{
	int i;
	GConnection *conn;
	for (i=0; i<[connections count]; i++) {
		conn = [connections objectAtIndex:i];
		
		if ([conn connected]) {
			[conn terminateConnection];
			usleep(400000);
			NSLog(@"Terminated connection to %@",[conn connectionName]);
		}
	}
}

-(BOOL)hasOpenConnections
{
	unsigned int i;
	
	for (i=0; i<[connections count]; i++) {
		if ([[connections objectAtIndex:i] connected] || [[connections objectAtIndex:i] connecting]) {
			//There are at least one open connection
			return YES;
		}
	}
	return NO;
}

-(void) getPassword:(GConnection*)connection
{
    [passwordLock lock];
    passwordConnection = connection;
    [self performSelectorOnMainThread:@selector(getPasswordMainThread)
                               withObject:nil
                            waitUntilDone:NO];
}

-(void) unlockPassword
{
    [passwordLock unlock];
}

-(void) getPasswordMainThread
{
    [passwordConnectionName setStringValue:passwordConnection.connectionName];
    [passwordUserName setStringValue:passwordConnection.tunnelUsername];
    [passwordHostName setStringValue:passwordConnection.tunnelHost];
    [passwordDialog makeKeyAndOrderFront:self];
}

@end
